Istio DestinationRule
개요
데스티네이션 룰은 라우팅될 목적지, 즉 클러스터에 대한 규칙을 작성하는 리소스이다.
버츄얼 서비스가 라우팅에 대한 설정을 명시했다면, 데룰은 버츄얼 서비스의 목적지로서 라우팅 후 관련한 설정을 명시한다.
클러스터의 개념이 익숙치 않다면 엔보이 참고.
버츄얼 서비스의 동작 방식을 먼저 이해하고 이 문서를 보길 권장한다.
기능
엔보이에서도 하나의 클러스터에는 여러 개의 엔드포인트, 즉 업스트림 호스트가 있을 수 있다.
데룰은 이러한 클러스터를 세분화하거나, 클러스터로 흘러들어가는 트래픽에 대한 정책을 지정한다.
목적지에 대한 규칙을 지정한다 해서 이름도 Destination Rule인 것이다!
- 서브셋 분할
- 하나의 클러스터로 묶인 엔드포인트들을 여러 부분집합으로 나눌 수 있다.
- 가령 한 서비스에 버전이 2개가 있는 상황일 때, 이것을 나누어 상세하게 라우팅할 수 있도록 정보를 제공하게 만들 수 있다.
- 트래픽 정책
- 로드밸런싱 정책을 설정할 수 있다.
- 이때 클러스터로 트래픽을 보낼 클라이언트 측 엔보이에 세팅이 되는 거라, 이런 방식을 클라이언트 로드밸런싱이라고 따로 부르기도 한다.
- 지역성(locality) 트래픽 로드 밸런싱도 가능하다.
- 서킷 브레이커로서 문제가 있는 호스트에 대해 트래픽이 가지 않도록 설정하는 것도 여기에서 진행된다.
- 트래픽 연결을 유지하는 커넥션 풀(connection pool) 설정
- 어떤 트래픽이 문제 있는 트래픽인지, 이상치 탐지(outlier detection)
- 로드밸런싱 정책을 설정할 수 있다.
- 통신 간 암호화
- 터널 세팅
이렇게 목적지를 세팅하는 행위는 네트워크 복원력(resilency), 혹은 서킷 브레이커 기능을 넣는 것과 같다.
가령 건강하지 않은 엔드포인트에는 트래픽을 보내지 않고, 레이턴시가 너무 긴 엔드포인트는 트래픽을 적게 보낸다던가 하는 식의 전략을 수립할 수 있는 게 바로 데스티네이션 룰이다.
그렇다고 이스티오에서 모든 트래픽 안정성을 위한 설정을 데스티네이션 룰에만 한다는 건 아니고, 당연히 버츄얼 서비스에서 재시도, 미러링 등의 동작을 시키는 것도 복원력을 위한 세팅이긴 하다.
동작
버츄얼 서비스와 달리 클러스터에 대한 설정을 하는 만큼, 설정 대상이 되는 엔보이를 찾는 작업이 조금 다르다.
일단 데룰은 서비스 레지스트리에 등록된 호스트를 기반으로 설정이 이뤄진다.
그 다음 이 클러스터 설정에 영향을 받을 엔보이를 탐색한다.
hosts
필드를 토대로 서비스 레지스트리에서 대상이 될 업스트림 클러스터를 정한다.- 해당 클러스터를 가지고 있는 모든 엔보이에 대해 관련 CDS 설정을 적용한다.
양식 작성법
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: bookinfo-ratings
spec:
host: ratings.prod.svc.cluster.local
workloadSelector:
matchLabels:
app: ratings
trafficPolicy:
loadBalancer:
simple: LEAST_REQUEST
subsets:
- name: testversion
labels:
version: v3
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
기본 필드 몇 개는 버츄얼 서비스와 비슷하다.
일단 hosts
필드를 지정하는데, 이 값은 서비스 레지스트리에 등록된 값이어야 한다.
이 값은 버츄얼 서비스와 마찬가지로 FQDN으로 두는 게 좋다.
여기에서도 exportTo
필드를 이용할 수 있는데, 이것은 이 데룰에 의한 클러스터 설정에 영향을 받을 엔보이의 네임스페이스를 지정한다.
여기에 더 세분화해서 workloadSelector
필드를 지정할 수 있다.
이걸 통해 네임스페이스 내에서 어떤 라벨을 가진 파드의 엔보이가 설정을 받을지 더 특정지을 수 있다.
subsets
subsets:
- name: version1
labels:
version: 1
- name: version2
labels:
version: 2
클러스터를 부분집합으로 나눌 때 사용하는 필드로, 라벨 셀렉터 기반으로 세부 집합을 나눈다.
보통 version 라벨을 설정해야 나중에 Kialli로도 보기 좋다.
(근데 아르고 롤아웃과 이스티오 연계할 때는 오히려 버전을 라벨로 두면 시각화가 힘들어진다..)
하위 필드로 바로 아래의 trafficPolicy
도 세팅할 수 있다!
trafficPolicy
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
tls:
credentialName: client-credential
mode: MUTUAL
트래픽 정책을 적용하는 필드로, 여기에 다양한 설정을 넣을 수 있다.
trafficPolicy:
portLevelSettings:
- port:
number: 31443
tls:
credentialName: client-credential
mode: MUTUAL
세부 필드를 알아보기 전에, 이 필드를 어디에 적용할 수 있는지 먼저 보겠다.
일단 이 필드에 하위 필드로 portLevelSettings
를 넣을 수 있는데, 트래픽 정책을 포트 단위로 적용할 수 있게 한다.
기본적으로 trafficPolicy
에 넣을 수 있는 필드를 거의 전부 넣을 수 있다.
subsets:
- name: testversion
labels:
version: v3
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
또한 설정을, 각 서브셋 별로 적용하는 것도 가능하다!
로밸, 커넥, 이상치, tls, 포트레벨, 터널, 프록시프로토로콜
loadBalancer
loadBalancer:
simple: ROUND_ROBIN
---
consistentHash:
httpCookie:
name: user
ttl: 0s
로드밸런싱 세팅을 하는 필드이다.
일단 크게 두 가지 방식이 있다.
- simple - 간단한 로드밸런싱 알고리즘을 적용할 때 사용하는 필드
- UNSPECIFIED - 이스티오가 알아서 설정!
- RANDOM
- PASSTHROUGH - 업스트림에 로드밸런싱 위임..즉 아무것도 안 함!
- ROUND_ROBIN
- LEAST_REQUEST - 요청이 적은 업스트림으로 많이 보내며, 일반적으로는 가장 좋다.
- consistentHash - HTTP 헤더, 쿠키 등의 속성을 통해 세션을 유지하는 로드 밸런싱을 세팅할 때 사용하는 필드
- 그러나 흔히 생각하는 세션 유지 정도의 기능을 담당할 순 없으니 참고.
- 가능한 필드는 다음과 같다.
- httpHeaderName
- httpCookie
- useSourceIp
- httpQueryParameterName
- ringHash
- maglev
localityLbSetting
loadBalancer:
simple: ROUND_ROBIN
localityLbSetting:
enabled: true
# 정적으로 분산 비율 지정
distribute:
- from: us-west/zone1/*
to:
"us-west/zone1/*": 80
"us-west/zone2/*": 20 # 합은 무조건 100이 돼야 하며, 명시되지 않은 지역으로는 트래픽이 가지 않는다.
- from: us-west/zone2/*
to:
"us-west/zone1/*": 20
"us-west/zone2/*": 80
---
# 한 지역이 실패 시 보낼 다른 지역 지정
failover:
- from: us-west
to: us-east
- from: us-east
to: eu-central
---
# 실패 시 가까운 지역을 매기는 순서
failoverPriority:
- "topology.istio.io/network"
- "topology.kubernetes.io/region"
- "topology.kubernetes.io/zone"
- "topology.istio.io/subzone"
지역적 로드밸런싱 세팅을 하는 필드로, 메시 전역적인 설정을 덮어쓰기한다.
클라우드 환경에서든, 멀티 클러스터 환경에서든 지역성 세팅을 해주는 것은 비용과 효율 측면에서 매우 중요하다.
이스티오에서는 {리전}/{존}/{서브존}
형태로 지역을 분류하는데, 쿠버네티스의 well-known 노드 라벨인 topology.kubernetes.io/*
를 기반으로 각 사이드카에도 해당 지역성 정보를 부여한다.
워크로드에 직접적으로 istio-locality: {리전}.{존}
과 같은 식으로 라벨을 달아도 지역 설정으로 인식된다.
만약 이런 정보를 얻을 수 없는 상태에서 지역적 로드밸런싱 세팅을 하게 될 경우, 이스티오는 엔보이에서 사용하는 방식을 따른다.[1]
세 가지 설정 방법이 있다.
- distribute - from에서 들어온 트래픽을 to로 보내는 비율 설정
- 참고로
enabled: true
만 하고 아무 설정 안 넣으면 각 지역의 엔드포인트 개수 기준으로 ditsribute가 설정된다.
- 참고로
- failover - from의 엔드포인트가 unhealthy할 때 to로 보냄
- 전부 unhealthy 해야만 보내는 건 아니고 이건 엔보이의 priority에 기반하는데, 바로 아래 failoverPriority 참고.[2]
- failoverPriority - 리스트 별로 한 다운스트림에 대한 여러 업스트림의 라우팅 우선순위 등급(priority)을 매김
- 리스트 첫번째부터, 일치하지 않는 값이 나올 때까지 연속된 리스트의 개수가 곧 해당 엔드포인트의 우선순위가 된다.
- 엄밀하게는 역순으로, 모든 리스트가 매칭되는 엔드포인트가 0 순위이다.
- 위 예시에서 1,2,4 번째 원소가 매칭되는 엔드포인트가 있다면, 1,2만 기준으로 평가되어 2순위가 된다.
- 키 값쌍을 넣어 더 정밀하게 우선순위를 부과할 수도 있다.
- 이 등급 별로 구분 짓고 healthy한 엔드포인트 퍼센티지를 따져 트래픽을 분산한다.[3]
- 리스트 첫번째부터, 일치하지 않는 값이 나올 때까지 연속된 리스트의 개수가 곧 해당 엔드포인트의 우선순위가 된다.
이 설정들은 독점적이라 서로 같이 사용할 수 없다!
왜냐, 각각의 설정이 실제 엔보이에 적용될 때 충돌을 일으키기 때문이다.
- distribute - 업스트림의 상태를 따지지 않고 우직하게 들어온 기준으로 분산
- failover - 업스트림의 상태를 보고 한 쪽에서 다른 쪽으로 모든 트래픽을 옮김
- failoverPriority - 일단 업스트림을 등급을 매긴 후에, 상태를 보면서 조금씩 트래픽 분산
일단 distribute는 정적으로 비율을 설정하기에 당연히 health를 따지는 failover와 충돌한다.
여기에 failoverPriority는 다른 것과 다르게 아예 등급을 매겨버리기 때문에 설정 방식부터가 다르다.
참고로 기본 이스티오 전역 설정에 지역적 로드밸런싱은 세팅돼있다.[4]
warmup
loadBalancer:
simple: ROUND_ROBIN
warmup:
duration: 10s
minimumPercent: 20.0
aggression: 10.0
새로운 엔드포인트가 추가됐을 때 워밍 업하는 기간을 주는 필드.
주어진 기간 동안 원래 가야하는 트래픽보다 적게 트래픽을 분배해주는데, 점진적으로 트래픽의 비중을 늘려준다.
connectionPool
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
connectTimeout: 30ms # 커넥션 맺을 때 기다리는 시간
tcpKeepalive: # idle 상태 유지 위해 주기적으로 작은 패킷 보내기
time: 7200s
interval: 75s
maxConnectionDuration: 1h # 한 커넥션 최대 유지 시간
idleTimeout: 1h # 트래픽 오가지 않는 상태에서 유효(idle)한 상태로 유지되는 최대 시간
업스트림 클러스터 각각의 커넥션 풀을 설정하는 필드이다.
즉 해당 클러스터를 대상으로 삼는 엔보이들은 각각 이 설정을 받게 된다.
근데 기본적으로는 분명 데룰을 대상으로 삼을 엔보이들의 cds가 설정을 받게 되는 것이지만, 몇 가지 필드들은 동작이 다르다!
가령 idleTimeout
필드는 사실 리스너 쪽에 설정되는 필드이고, 그래서 데룰의 대상이 된 워크로드가 가진 엔보이에 LDS 설정이 가해진다.
그래서 가중치 관련 세팅이 된 환경에서 이 값이 가중치따라 개별 적용되는 것은 불가능하다.
리스너 관련 설정에 대한 이야기는 이스티오 사이드카 쪽에서도 나오니 해당 부분을 조금 더 참고하자.
trafficPolicy:
connectionPool:
http:
http1MaxPendingRequests: 100 # 풀이 꽉 찼을 때 큐에 저장해둘 최대 요청 수
http2MaxRequests: 1000 # 풀 크기
maxRequestsPerConnection: 10 # 한 tcp 커넥션에서 처리할 요청 개수(풀의 한 원소)
maxRetries: 3
idleTimeout: 30s
h2UpgradePolicy: UPGRADE # http1에서 http2로의 업그레이드 정책
useClientProtocol: false # 클라의 프로토콜을 그대로 쓸지
maxConcurrentStreams: 256 # http2에서 최대 동시 스트림 수
http에 대해서도 설정할 수 있다.
여기에서 커넥션 풀의 개별 단위는 tcp인 것은 동일하다.
http2MaxRequests
필드가 나타내는 것이 전체 풀 크기를 나타내는 것이라고 했는데, 이름만 보면 HTTP/2로 한정짓는 것처럼 보인다.
그러나 이건 잘못 이름 지어진 것으로, 엔보이의 설정 이름을 따라했다가 수정사항은 팔로업하지 않아서 생긴 이슈이다.[5]
outlierDetection
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http2MaxRequests: 1000
maxRequestsPerConnection: 10
outlierDetection:
consecutiveGatewayErrors: 2 # 기본적인 이상치 기준
splitExternalLocalOriginErrors: true # 로컬 오류와 업스트림 오류 구분할지
consecutiveLocalOriginFailures: 3 # 로컬 오류를 구분한다면 로컬에서 몇 번 에러날 때 제외할지
consecutive5xxErrors: 5 # http 한정 이상치 기준
interval: 10s
baseEjectionTime: 30s # 기본 제외 기간으로, 지수적 증가
maxEjectionPercent: 20 # 최대 제외 가능한 호스트 수
minHealthPercent: 50 # 이상치 탐지 중단할 최소 헬시 퍼센트
이상치 탐지를 통해 서킷 브레이커를 하는 필드.
연속적인 오류를 이상치 패턴으로 감지 - 이상치를 넘긴 호스트를 잠시 제외(ejection) - 일정 시간 후 재검증과 같은 식으로 동작한다.
이상치라고 탐지된 엔드포인트는 잠시 unhealthy 상태로 체크하고 로드밸런싱 풀에서 잠시 제외된다.
이것도 http, tcp 각각에 적용 가능하며, 가령 다음과 같은 식이다.
- http - 500에러가 날 때
- tcp - 커넥션 타임아웃이나 실패가 뜰 때
로컬 오류와 업스트림 오류를 구분한다는 것은 다음과 같은 식이다.
a에서 b로 가는 요청이 있고, a에서 타임아웃 기간을 1초로 잡았다.
이 타임아웃으로 인해 발생하는 오류와, 실제로 b가 뱉어내는 오류를 구분하고 싶다면 splitExternalLocalOriginErrors
를 true 걸면 된다.
consecutiveGatewayErrors
필드는 http 에러도 이상치로 잡기 때문에 consecutive5xxErrors
와 같이 쓴다면 조금 더 높게 잡는 것이 일반적이다.
maxEjectionPercent
는 이상치로 둘 수 있는 최대 업스트림의 퍼센트를 나타내며, 이 이상을 넘어가면 이상치로 보이더라도 제외시키진 않는다.
minHealthPercent
는 이상치 탐지를 활성화하는 최소 healthy 업스트림의 퍼센트를 나타낸다.
가령 이 값이 50으로 설정돼있는데 이상치 탐지를 하다보니 healthy한 업스트림이 50퍼 미만으로 내려가버린다면, 그냥 이상치 탐지가 완전히 중단되고 unhealthy 해도 트래픽을 날려버린다!
기본값은 0인데, 이 값은 이상치 탐지를 비활성화하지 않고 계속 활성화한다.
tls
trafficPolicy:
tls:
mode: MUTUAL
#clientCertificate: /etc/certs/myclientcert.pem # 이건 직접 파일 경로를 명시할 때 쓰는 방식
#privateKey: /etc/certs/client_private_key.pem
#caCertificates: /etc/certs/rootcacerts.pem
#caCrl: /etc/certs/revoked.pem # 회수된 인증서 목록
credentialName: client-cert-secret # 쿠버 시크릿을 이용하는 방식
subjectAltNames: # 명시적으로 SAN 검증
- api.credential.com
sni: api.credential.com # tls 클라 헬로 시 전달할 sni 필드
insecureSkipVerify: false
업스트림과의 연결 간 TLS 관련 설정을 하는 필드이다.
mode
에 ISTIO_MUTUAL를 쓰면 그냥 이스티오 전역 설정에 맡기는 방식으로, 다른 필드를 쓸 수 없다.
subjectAltNames
필드는 이스티오 서비스엔트리의 SAN값을 덮어쓰기한다.
credentialName
은 쿠버네티스 시크릿을 이용하는 방식인데, 위에 주석 처리된 값들을 넣어서 사용할 수 있다.
시크릿의 키값은 key, cert, cacert, crl로 돼있어야 한다.
mtls일 때는 대체로 같은 서비스 간 cacert를 쓸 텐데, {시크릿 이름}-cacert
라는 이름에 cacert가 있으면 이걸 읽어주기에 하나로 관리할 수 있다.
이 방식에 주의점이 있는데, 데룰에 workloadSelector
가 명시돼야만 이 방식이 가능하다.
기본적으로 각 엔보이 프록시는 SDS를 통해 인증서 설정이 이뤄지는데, 명시적으로 파드가 제한되지 않으면 중요한 보안의 지점 중 하나인 인증서를 그냥 무턱대고 모든 엔보이에 주입하는 일이 발생할 수 있다.
그래서 이스티오에서는 명시적으로 workloadSelector
를 지정한 케이스에 대해서만 시크릿을 사용한 방식이 가능하도록 제한해둔다.
참고로 이스티오 게이트웨이는 여타 서비스와 다른 진입점이다보니 예외적으로 허용하는 것 같다.
위 설정 방식에 대한 이유는 내가 직접 생각해본 것이다.
아닐 수도 있다는 것에 유의하자.
사이드카와 게이트웨이 간 시크릿을 이용하는 방법에 대한 차이에 대한 글을 나중에 발견하면 추가로 정리하겠다.
근데 tls 설정은 보통 이스티오 전역적으로 하는 경우가 대부분이라, 이렇게 데룰에다 세팅을 하는 케이스는 많이 없다.
- 이스티오 서비스엔트리로 등록한 외부 업스트림에 대해 설정
- 자동 mTLS를 끄고 명시적으로 mTLS를 강제할 때
- 드물지만 이스티오가 자동으로 사이드카를 삽입하지 못하거나, 서비스 탐지를 실패한다면 이런 세팅이 유효하다고 한다.
- tls 인증서 자체를 커스터마이징해야 할 때
이런 케이스들에 대해서 사용할 만한 정도라 보면 되겠다.
tunnel
trafficPolicy:
tunnelSettings:
protocol: CONNECT
targetHost: 10.0.1.23
targetPort: 8080
TCP 터널링을 설정하는 필드이다.
참고로 이 세팅은 http 라우팅으로 들어온 요청에 대해 적용되지 않는다는 것 같은데, 이건 조금 확인해봐야할 것 같다.
다운스트림에서 오는 요청을 protocol
필드로 지정하는데 CONNECT, POST가 가능하다.
proxyProtocol
프록시 버전을 세팅하는 필드로, V1, V2가 있다.
관련 문서
이름 | noteType | created |
---|
참고
https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/locality_weight ↩︎
https://istio.io/latest/docs/tasks/traffic-management/locality-load-balancing/failover/ ↩︎
https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/priority ↩︎
https://istio.io/latest/docs/reference/config/istio.mesh.v1alpha1/ ↩︎